iT邦幫忙

2024 iThome 鐵人賽

DAY 5
0
JavaScript

Don't make JavaScript Just Surpise系列 第 5 篇

原始型別轉換與比較

  • 分享至 

  • xImage
  •  

昨天的內容介紹了關於各個原始型別基本的特性。
今天主要就幾個主題來討論:

  • typeof
  • == 和 ===
  • 隱式轉換(implicit coercion)和顯式轉換(explicit conversion)

typeof

typeof 是一個原生的運算符,給予一個變數,返回一個字串表示該變數的型別。

console.log(typeof 1);//number
console.log(typeof 1n);//bigint
console.log(typeof 'abc');//string
console.log(typeof undefined);//undefined
console.log(typeof true);//boolean
console.log(typeof Symbol('foo'));//symbol

//複合型別
console.log(typeof {});//object
console.log(typeof function foo());//function

以上是所有目前版本 typeof 可能的回傳值。
這個函式其實有不少需要注意的例外,比如原始型別中的 null

console.log(typeof null);//object

typoeof null 的情況會回傳為 object,這個是一個歷史悠久的錯誤,但因為現在可能有許多網路上的實作基於這個前提,目前改也是改不動,就這樣一直將錯就錯了。

上面展示複合型別的結果的時候,可以留意到,嚴格來說 function 其實也算一種 object 的子型別,但 typeof 會特別顯示為 function 而非 object,讓我們在使用上更容易去判斷函式。
其餘的子型別如 array,則一律回傳為 object,需要判斷型別的話,得用如 Array.isArray() 等特別的函式來做判斷。

而且 typeof 是一個好用於判斷 undefined 的方式,在 JS 中直接使用 undefiend 往往會遇到例外,但使用 typeof 能夠安全的判斷是否為 undefined 再繼續做接下來的行為。

== 和 ===

一個等於是賦值,兩個等於和三個等於都是用於比較。
兩個等於稱作相等運算符,三個等於稱作嚴格相等運算符。
來看一個最簡單的例子:

let a = '1';
let b = 1;
console.log(a == b);//true
console.log(a === b);//false

等於做的事情是 JS 會先判斷兩方的型別是否相等,如不相等,則嘗試轉型為兩方一致後再進行比較。
相等運算符的隱式轉換依據以下原則進行:

  1. 數字和字串比較:將字串轉為數字進行比較(如上面的例子)
  2. 和布林值比較:布林值會被轉為數字,true 轉為 1,false 轉為 0。
    console.log(true == 1);//true
    console.log(false == 0);//true
    console.log(true == "a");//false
    
  3. null 和 undefined 進行相等運算符的時候會相等,但和其他任何型別皆會不相等。
    console.log(undefined == null);//true
    console.log(null == false);//false
    console.log(undefined == '1');//false
    
  4. 物件和原始型別進行比較,嘗試轉為對應的原始型別,依據物件本身的實作(toString, valueOf)或物件型別的預設值進行轉換
    let arr = [1,2];
    console.log(arr == '1,2');//true
    console.log(arr.toString());//"1,2"
    
  5. NaN 前面介紹 Number 時沒有提到,他是 nubmer 型別中的一員,用於表示 Not a Number,比如使用 Number() 嘗試對一個無法轉為 number 型別的對象進行轉型,或是數學運算無法計算/無法定義結果的情況,就會遇到這個回傳。
    console.log(Number('a'));//NaN
    console.log(1/0);//Infinity
    console.log(0/0);//NaN
    console.log(typeof (0/0));//number
    
    同時,NaN 是 JavaScript 中唯一一個自身與自身並不相等的變數,這意味著如果要判斷是否 NaN 時無法使用相等運算符來判斷,必須使用 Number.isNaN 的函式來判斷。
    console.log(Infinity == Infinity);//true
    console.log(NaN == NaN);//false
    console.log(Number.isNaN(Number({})));//true
    

嚴格相等運算符 === 則是不會進行 隱式轉換,直接進行比較。

let a = '1';
let b = 1;
console.log(a == b);//true
console.log(a === b);//false

容我複製上面的例子,a 和 b 進行嚴格相等運算符比較時,會拿到 false 的結果,因為字串和數字並不相等。

如果你對隱式轉換會發生的事情不夠確定,也不確定自己拿到的內容,最佳實踐上會是盡量在能夠使用嚴格運算符 === 的時候就使用 === 嚴格運算符。
在 YDKJS 中,作者其實對 == 的隱式轉換並不是持有強烈的反對態度,而是表示我們應該要去理解隱式轉換中發生的事情,並在明確知道意圖的情況下可以使用隱式轉換來獲得一些好處。
那讓我們接著看下一個主題:隱式轉換和顯示轉換。

隱式轉換(implicit coercion)和顯式轉換(explicit coercion)

前面其實已經數次提到這兩個詞。
型別轉換是程式中的常見場景,用於將一個型別的變數轉換為另一個型別。在 JavaScript 這種動態類型語言中,轉換常被稱為強制型別轉換(coercion)。
依據轉換的方式,我們可以再分為顯示轉換和隱式轉換。
顯示轉換表示在程式碼中我們明確使用了某個函數或方法來進行型別的轉換,例如:

let a = 1;
console.log(a);//1
console.log(String(a));//"1"

這個例子中的 String() 明確表示了轉換的意圖,即為顯示轉換。

let a = 1;
let b = '1';
console.log(a == b);//true

複習上一個段落提到的,相等運算符會進行隱式轉換,上面的程式碼中我們沒有直接看到轉換的意圖,但在相等運算符的機制下,他進行了隱式轉換把字串轉為數字進行比較。

隱式轉換常見於各種場景,隱式轉換的結果必定被轉換為一個原始型別,大多是轉換為 字串,數字,或是布林值。除了相等運算符,我們可以再看一下幾個例子:

  1. 加法運算符(+)
    此種運算符除了數字的相加外,還會用於字串連接。
    只要有字串參與,不管順序,一律轉為字串處理。
let a = 1;
console.log(typeof ("" + a));//string 
console.log(typeof (a + ""));//string 
console.log(typeof ({} + ""));//string
  1. 非加法運算符(-, *, /)
    一般此種運算符僅會用於數字運算。
    如果有字串參與,則會嘗試將字串轉為數字,若轉換失敗,無法計算時會變為 NaN。
console.log(2-"1");//1
console.log(2-"");//2,空字串會被轉換為 0
console.log(2-"a");//NaN
  1. 布林值的轉換
    在 JS 中,有個列表稱作 falsy,用於表示在轉為 boolean 值時(如進行邏輯運算比較),會被轉換為 false 的值,包含以下幾種:
  • false 和其本身
  • 0 和 -0
  • '' 和 "" 和 `` 等空字串
  • null
  • undefined
  • NaN
console.log(Boolean(false));
console.log(Boolean(0), Boolean(-0));
console.log(Boolean(''),Boolean(""),Boolean(``));
console.log(Boolean(null));
console.log(Boolean(undefined));
console.log(Boolean(NaN));
//全部都是 false

為什麼要提到 falsy 列表?因為 boolean 在 JS 中只有兩種值,true 和 false,所有不在 falsy 列表中的值,進行轉換成 boolean 的行為時,永遠會是 true,又稱為 truthy。
只要記住這些 falsy 的值,剩下就都是 truthy 了,也許是做個筆記或是背一下,這個列表還是很有用的。

//即使這種 case 「感覺」上可能是 false,但正如上面說的,只要不在 falsy 表裡,就會是 true
console.log(Boolean({}));//true
console.log(Boolean([]));//true

那什麼情況會隱式轉換一個值為 boolean 值?
我們可以使用 ! 反邏輯運算符來進行轉換,而因為這樣會導致值的相反,開發者會使用 !! 來得到一個與該值相對應的布林值。

console.log(typeof 2);//number
console.log(!2,typeof !2);//false , boolean
console.log(!!2,typeof !!2);//true , boolean

其他 - 剖析器(Parser)和強制型別轉換

常使用 JS 的人應該對 Parser 並不陌生,我指的 Parser 是如同:

let weight = '100g';
let weightValue = Number.parseInt(weight);
console.log(weight, weightValue);//"100g", 100
console.log(Number(weight));//NaN

像 Number.parseInt 這樣的存在,Parser 往往具有一些定義好的規則,以 Number.parseInt 為例,他的剖析規則是從左往右確認輸入,直到找到第一個無法轉為 Number 的數字停下來(g),剖析該字元之前的數字。
不同的是,這個情況使用 Number() 的方式來進行強制型別轉換則是出現 NaN ,因為強制型別轉換並沒有像剖析器這樣定義特別規則。
特別提一下關於這點來表明剖析器和本文探討的強制型別轉換是兩種不同的機制。

其他 - new String 和 String

在強制型別轉換中,會時常看到我們使用 String(), Number() 等方式來進行強制型別轉換。
注意!在沒有意識的情況,不要寫成 new String()。
兩者的差異讓我們來看程式碼:

let a = 456;
let aStr = String(a);
let aStr2 = new String(a);
console.log(aStr, typeof aStr);//"456", "string"
console.log(aStr2, typeof aStr2);//"456", "object"

雖然印出來都是 "456",但是因為 console.log 的情況隱含了 .toString() 的隱式型別轉換。
用 typeof 可以看出來,加入了 new 的關鍵字後,變成了透過建構的方式創建了一個之前提到的 String 包裝物件,型別為物件 Object 而不再是 String。
一般來說,包裝物件的初始化多留給 JS 引擎去在背後處理,若一般開發中需要用到轉型,應該先轉型後再去使用該型別的對應方法。


上一篇
原始型別的宣告與特性
下一篇
物件(object)與複製行為
系列文
Don't make JavaScript Just Surpise 共 31 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言